/*! store2 - v2.10.0 - 2019-09-27
 * Copyright (c) 2019 Nathan Bubna; Licensed (MIT OR GPL-3.0) */
(function(window, define) {
    var _ = {
        version: '2.10.0',
        areas: {},
        apis: {},

        // utilities
        inherit: function(api, o) {
            for (var p in api) {
                if (!o.hasOwnProperty(p)) {
                    Object.defineProperty(o, p, Object.getOwnPropertyDescriptor(api, p));
                }
            }
            return o;
        },
        stringify: function(d) {
            return d === undefined || typeof d === 'function' ? d + '' : JSON.stringify(d);
        },
        parse: function(s) {
            // if it doesn't parse, return as is
            try {
                return JSON.parse(s);
            } catch (e) {
                return s;
            }
        },

        // extension hooks
        fn: function(name, fn) {
            _.storeAPI[name] = fn;
            for (var api in _.apis) {
                _.apis[api][name] = fn;
            }
        },
        get: function(area, key) {
            return area.getItem(key);
        },
        set: function(area, key, string) {
            area.setItem(key, string);
        },
        remove: function(area, key) {
            area.removeItem(key);
        },
        key: function(area, i) {
            return area.key(i);
        },
        length: function(area) {
            return area.length;
        },
        clear: function(area) {
            area.clear();
        },

        // core functions
        Store: function(id, area, namespace) {
            var store = _.inherit(_.storeAPI, function(key, data, overwrite) {
                if (arguments.length === 0) {
                    return store.getAll();
                }
                if (typeof data === 'function') {
                    return store.transact(key, data, overwrite);
                } // fn=data, alt=overwrite
                if (data !== undefined) {
                    return store.set(key, data, overwrite);
                }
                if (typeof key === 'string' || typeof key === 'number') {
                    return store.get(key);
                }
                if (typeof key === 'function') {
                    return store.each(key);
                }
                if (!key) {
                    return store.clear();
                }
                return store.setAll(key, data); // overwrite=data, data=key
            });
            store._id = id;
            try {
                var testKey = '_-bad-_';
                area.setItem(testKey, 'wolf');
                store._area = area;
                area.removeItem(testKey);
            } catch (e) {}
            if (!store._area) {
                store._area = _.storage('fake');
            }
            store._ns = namespace || '';
            if (!_.areas[id]) {
                _.areas[id] = store._area;
            }
            if (!_.apis[store._ns + store._id]) {
                _.apis[store._ns + store._id] = store;
            }
            return store;
        },
        storeAPI: {
            // admin functions
            area: function(id, area) {
                var store = this[id];
                if (!store || !store.area) {
                    store = _.Store(id, area, this._ns); //new area-specific api in this namespace
                    if (!this[id]) {
                        this[id] = store;
                    }
                }
                return store;
            },
            namespace: function(namespace, singleArea) {
                if (!namespace) {
                    return this._ns ? this._ns.substring(0, this._ns.length - 1) : '';
                }
                var ns = namespace,
                    store = this[ns];
                if (!store || !store.namespace) {
                    store = _.Store(this._id, this._area, this._ns + ns + '.'); //new namespaced api
                    if (!this[ns]) {
                        this[ns] = store;
                    }
                    if (!singleArea) {
                        for (var name in _.areas) {
                            store.area(name, _.areas[name]);
                        }
                    }
                }
                return store;
            },
            isFake: function() {
                return this._area.name === 'fake';
            },
            toString: function() {
                return 'store' + (this._ns ? '.' + this.namespace() : '') + '[' + this._id + ']';
            },

            // storage functions
            has: function(key) {
                if (this._area.has) {
                    return this._area.has(this._in(key)); //extension hook
                }
                return !!(this._in(key) in this._area);
            },
            size: function() {
                return this.keys().length;
            },
            each: function(fn, fill) {
                // fill is used by keys(fillList) and getAll(fillList))
                for (var i = 0, m = _.length(this._area); i < m; i++) {
                    var key = this._out(_.key(this._area, i));
                    if (key !== undefined) {
                        if (fn.call(this, key, this.get(key), fill) === false) {
                            break;
                        }
                    }
                    if (m > _.length(this._area)) {
                        m--;
                        i--;
                    } // in case of removeItem
                }
                return fill || this;
            },
            keys: function(fillList) {
                return this.each(function(k, v, list) {
                    list.push(k);
                }, fillList || []);
            },
            get: function(key, alt) {
                var s = _.get(this._area, this._in(key));
                return s !== null ? _.parse(s) : alt || s; // support alt for easy default mgmt
            },
            getAll: function(fillObj) {
                return this.each(function(k, v, all) {
                    all[k] = v;
                }, fillObj || {});
            },
            transact: function(key, fn, alt) {
                var val = this.get(key, alt),
                    ret = fn(val);
                this.set(key, ret === undefined ? val : ret);
                return this;
            },
            set: function(key, data, overwrite) {
                var d = this.get(key);
                if (d != null && overwrite === false) {
                    return data;
                }
                return _.set(this._area, this._in(key), _.stringify(data), overwrite) || d;
            },
            setAll: function(data, overwrite) {
                var changed, val;
                for (var key in data) {
                    val = data[key];
                    if (this.set(key, val, overwrite) !== val) {
                        changed = true;
                    }
                }
                return changed;
            },
            add: function(key, data) {
                var d = this.get(key);
                if (d instanceof Array) {
                    data = d.concat(data);
                } else if (d !== null) {
                    var type = typeof d;
                    if (type === typeof data && type === 'object') {
                        for (var k in data) {
                            d[k] = data[k];
                        }
                        data = d;
                    } else {
                        data = d + data;
                    }
                }
                _.set(this._area, this._in(key), _.stringify(data));
                return data;
            },
            remove: function(key, alt) {
                var d = this.get(key, alt);
                _.remove(this._area, this._in(key));
                return d;
            },
            clear: function() {
                if (!this._ns) {
                    _.clear(this._area);
                } else {
                    this.each(function(k) {
                        _.remove(this._area, this._in(k));
                    }, 1);
                }
                return this;
            },
            clearAll: function() {
                var area = this._area;
                for (var id in _.areas) {
                    if (_.areas.hasOwnProperty(id)) {
                        this._area = _.areas[id];
                        this.clear();
                    }
                }
                this._area = area;
                return this;
            },

            // internal use functions
            _in: function(k) {
                if (typeof k !== 'string') {
                    k = _.stringify(k);
                }
                return this._ns ? this._ns + k : k;
            },
            _out: function(k) {
                return this._ns
                    ? k && k.indexOf(this._ns) === 0
                        ? k.substring(this._ns.length)
                        : undefined // so each() knows to skip it
                    : k;
            }
        }, // end _.storeAPI
        storage: function(name) {
            return _.inherit(_.storageAPI, { items: {}, name: name });
        },
        storageAPI: {
            length: 0,
            has: function(k) {
                return this.items.hasOwnProperty(k);
            },
            key: function(i) {
                var c = 0;
                for (var k in this.items) {
                    if (this.has(k) && i === c++) {
                        return k;
                    }
                }
            },
            setItem: function(k, v) {
                if (!this.has(k)) {
                    this.length++;
                }
                this.items[k] = v;
            },
            removeItem: function(k) {
                if (this.has(k)) {
                    delete this.items[k];
                    this.length--;
                }
            },
            getItem: function(k) {
                return this.has(k) ? this.items[k] : null;
            },
            clear: function() {
                for (var k in this.items) {
                    this.removeItem(k);
                }
            }
        } // end _.storageAPI
    };

    var store =
        // safely set this up (throws error in IE10/32bit mode for local files)
        _.Store(
            'local',
            (function() {
                try {
                    return localStorage;
                } catch (e) {}
            })()
        );
    store.local = store; // for completeness
    store._ = _; // for extenders and debuggers...
    // safely setup store.session (throws exception in FF for file:/// urls)
    store.area(
        'session',
        (function() {
            try {
                return sessionStorage;
            } catch (e) {}
        })()
    );
    store.area('page', _.storage('page'));

    if (typeof define === 'function' && define.amd !== undefined) {
        define('store2', [], function() {
            return store;
        });
    } else if (typeof module !== 'undefined' && module.exports) {
        module.exports = store;
    } else {
        // expose the primary store fn to the global object and save conflicts
        if (window.store) {
            _.conflict = window.store;
        }
        window.store = store;
    }
})(this, this && this.define);
